<?php

class CI_Mongodb{
	
	//Mongo对象
	protected $_mongo = NULL;
	
	//MongoDb对象
	protected $_mongodb = NULL;

	//MongoCollection对象
	protected $_collection = NULL;
	
	//MongoCursor对象
	protected $_cursor = NULL;
	
	//调试模式
	protected $_debug = TRUE;
	
	//错误信息
	protected $_error = '';
	
	//主键名称
	protected $_pk = 'id';
	
	//是否返回id字段，说明：mongodb始终返回_id字段
	protected $_return_pk = TRUE;
	
	//当前连接数据库名称
	protected $_db;
	
	//所有数据库
	protected $_dbs = array();
	
	//当前连接数据库的所有数据表
	protected $_tables = array();
	
	public function __construct()
	{
		if ( ! class_exists('Mongo'))
		{
			show_error('Mongo未安装或未开启');
		}

		$ci = & get_instance();
		$ci->config->load('mongodb', TRUE);
		$config = $ci->config->item('mongodb');
		//与主机建立连接
		$this->connect($config['db_host'], $config['db_port'], $config['db_user'], $config['db_pass']);
		//选择数据库
		$this->select_db($config['db_name']);

		log_message('debug', '成功连接mongodb数据库');
	}
	
	/*
	 * 设置、返回错误信息
	 */
	public function error($error = NULL)
	{
		if (NULL===$error)
		{
			return $this->_error;
		}
		else
		{
			$this->_error = 'MongoDB：'.$error;
			
			if ($this->_debug)
			{
				//调试模式，直接显示错误
				show_error($this->_error);
			}
			else
			{
				//记录错误日志
				log_message('error', $this->_error);
			}
		}
	}
	
	/*
	 * 连接数据库
	 * 成功返回true，失败返回false
	 */
	public function connect($db_host = 'localhost', $db_port = 27017, $db_user = '', $db_pass = '')
	{
		$server = 'mongodb://'.$db_user.($db_pass ? (':'.$db_pass.'@') : '').$db_host.':'.$db_port;
		try
		{
			$this->_mongo = new Mongo($server);
			return TRUE;
		}
		catch (MongoConnectionException $e)
		{
			$this->error('连接主机失败，错误信息：'.$e->getMessage());
			return FALSE;
		}
	}
	
	/*
	 * 连接数据库
	 * 成功返回true，失败返回false
	 */
	public function select_db($db)
	{
		try
		{
			$this->_mongodb = $this->_mongo->selectDB($db);
			$this->_db = $db;
			$this->_tables = array();
			return TRUE;
		}
		catch (InvalidArgumentException $e)
		{
			$this->error = '连接数据库'.$db.'失败，错误信息：'.$e->getMessage();
			return FALSE;
		}
	}
	
	/*
	 * 创建数据库
	 * 成功返回true，失败返回false
	 */
	public function create_db($db)
	{
		if (in_array($db, $this->list_dbs()))
		{
			$this->error('数据库'.$db.'已经存在');
			return FALSE;
		}
		$this->_mongo->selectDB($db)->execute("function(){}");
		array_push($this->_dbs, $db);
		return TRUE;
	}
	
	/*
	 * 删除数据库
	 * 成功返回true，失败返回false
	 */
	public function drop_db($db)
	{
		if ($db==$this->_db)
		{
			$this->error('不能删除当前连接的数据库');
			return FALSE;
		}
		
		$this->_mongo->selectDB($db)->drop();
		foreach ($this->_dbs as $k => $v)
		{
			if ($v==$db)
			{
				unset($this->_dbs[$k]);
				break;
			}
		}
		$this->select_db($this->_db);
		return TRUE;
	}
	
	/*
	 * 所有数据库，所有数据库名称组成的数组
	 */
	public function list_dbs()
	{
		if ($this->_dbs)
		{
			return $this->_dbs;
		}

		$dbs = array();
		$list = $this->_mongo->listDBs();
		foreach ($list['databases'] as $row)
		{
			$dbs[] = $row['name'];
		}
		$this->_dbs = $dbs;
		return $dbs;
	}
	
	/*
	 * 创建表
	 * name	表名称
	 * is_capped	表是否固定大小
	 * size	如果是固定大小，表的字节大小
	 * max	如果是固定大小，表的最大记录数
	 * 成功返回true，失败返回false
	 */
	public function create_table($table, $is_capped = FALSE, $size = 0, $max = 0)
	{
		$table = trim($table);
		
		if ( ! $table)
		{
			trigger_error('数据表名称不能为空');
			return FALSE;
		}

		if (in_array($table, $this->list_tables()))
		{
			$this->error('数据表'.$table.'已经存在');
			return FALSE;
		}

		//建表
		$this->_mongodb->createCollection($table, $is_capped, $size, $max);
		array_push($this->_tables, $table);
		
		//建时间戳索引
		$this->create_index($table, 'timestamp', array('timestamp'=>'desc'));
		return TRUE;
	}
	
	/*
	 * 删除表
	 * 始终返回true
	 */
	public function drop_table($table)
	{
		if ( ! in_array($table, $this->list_tables()))
		{
			return TRUE;
		}

		$this->_mongodb->selectCollection($table)->drop();
		foreach ($this->_tables as $k => $v)
		{
			if ($v==$table)
			{
				unset($this->_tables[$k]);
				break;
			}
		}
		return TRUE;
	}
	
	/*
	 * 数据库所有表，这个数据库所有表名组成的数组
	 */
	public function list_tables()
	{
		if ($this->_tables)
		{
			return $this->_tables;
		}
		$list = $this->_mongodb->listCollections();
		$tables = array();
		if ($list)
		{
			foreach ($list as $row)
			{
				$tables[] = $row->getName();
			}
		}
		$this->_tables = $tables;
		return $tables;
	}
	
	/*
	 * 数据表是否存在
	 * 存在返回true，不存在返回false
	 */
	public function table_exist($table)
	{
		return in_array($table, $this->list_tables()) ? TRUE : FALSE;
	}
	
	/*
	 * 创建索引
	 * table	表名称
	 * index	索引名称
	 * keys	索引字段，数组，例如array('a'=>'desc','b'=>'asc')，创建一个a字段递减，b字段递增的索引
	 * unique	是否唯一索引
	 * remove	如果是唯一索引，是否删除重复值
	 */
	public function create_index($table, $index, $keys, $unique = FALSE, $remove = FALSE)
	{
		$table = trim($table);
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		
		$index = trim($index);
		if ( ! $index)
		{
			trigger_error('索引名称不能为空');
			return FALSE;
		}
		
		foreach ($keys as &$v)
		{
			if ($v=='desc')
			{
				$v = -1;
			}
			elseif ($v=='asc')
			{
				$v = 1;
			}
		}
		
		$index = '_'.$index.'_';
		$options = array('safe'=>TRUE);
		
		$indexes = $this->_mongodb->$table->getIndexInfo();
		if ($indexes)
		{
			foreach ($indexes as $row)
			{
				if ($row['name']==$index)
				{
					$this->error('数据表'.$table.'索引'.$index.'已经存在');
					return FALSE;
				}
			}
		}
		
		$options['name'] = $index;
		if ($unique)
		{
			$options['unique'] = 1;
			$options['dropDups'] = intval($remove);
		}
		
		try
		{
			$this->_mongodb->$table->ensureIndex($keys, $options);
			return TRUE;
		}
		catch (MongoCursorException $e)
		{
			$this->error('数据库'.$table.'创建索'.$index.'引失败，错误信息：'.$e->getMessage());
			return FALSE;
		}
	}
	
	/*
	 * 删除索引
	 * table	表名称
	 * index	索引名称
	 * 成功返回true，失败返回false
	 */
	public function drop_index($table, $index)
	{
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		
		$index = trim($index);
		if ( ! $index)
		{
			trigger_error('索引名称不能为空', E_USER_ERROR);
			return FALSE;
		}
		$index = '_'.$index.'_';
		
		if (in_array($index, array('_timestamp_','_id_')))
		{
			$this->error('数据表'.$table.'索引'.trim($index, '_').'不能删除');
			return FALSE;
		}
		
		$indexes = $this->_mongodb->$table->getIndexInfo();
		if ($indexes)
		{
			foreach ($indexes as $row)
			{
				if ($row['name']==$index)
				{
					$this->_mongodb->command(array("deleteIndexes" => $this->_mongodb->$table->getName(), "index" => $row["name"]));
					return TRUE;
				}
			}
		}
		$this->error('数据表'.$table.'索引'.$index.'不存在');
		return FALSE;
	}

	/*
	 * 根据条件返回一条数据
	 * table	数据表名
	 * condition	条件
	 * fields	要返回的字段，数组或字符串（以,号隔开）
	 */
	public function get_one($table, $condition = array(), $fields = NULL)
	{
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		
		$fields = $this->parse_fields($fields);
		$query = $this->parse_query($condition);
		$sort = $this->parse_sort($condition);
		
		try
		{
			$this->_cursor = $this->_mongodb->selectCollection($table)->find($query, $fields)->sort($sort)->limit(1);
		}
		catch (MongoCursorException $e)
		{
			$this->error = '查询失败，错误信息：'.$e->getMessage();
		}

		$results = $this->parse_cursor();
		return $results ? current($results) : array();
	}
	
	/*
	 * 返回多条记录
	 * condition	数组条件，数组
	 * fields	要返回的字段，数组或字符串（以,号隔开）
	 * start	从哪行开始，整数
	 * limit	返回条数，整数
	 */
	public function get_list($table, $condition = array(), $limit = 0, $fields = NULL)
	{
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		
		$fields = $this->parse_fields($fields);
		$query = $this->parse_query($condition);
		$sort = $this->parse_sort($condition);
		$limit = $this->parse_limit($limit);
		
		try
		{
			$this->_cursor = $this->_mongodb->selectCollection($table)->find($query, $fields)->sort($sort)->skip($limit[0])->limit($limit[1]);
		}
		catch (MongoCursorException $e)
		{
			$this->error = '查询失败，错误信息：'.$e->getMessage();
		}
		return $this->parse_cursor();
	}
	
	/*
	 * 插入数据
	 * table	表名
	 * data	数据，数组
	 */
	public function insert($table, $data)
	{
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		
		$data = $this->parse_data($data);
		
		$data['timestamp'] = date('Y-m-d H:i:s');

		try
		{
			$this->_mongodb->$table->insert($data, array('safe'=>TRUE));
			return TRUE;
		}
		catch (MongoCursorException $e)
		{
			$error = $e->getMessage();
			return FALSE;
		}
	}
	
	/*
	 * 更新数据
	 * table	表名
	 * data	修改数据，数组
	 * condition	条件，数组
	 */
	public function update($table, $data, $condition = array())
	{
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		
		$query = $this->parse_query($condition);
		$data = $this->parse_data($data);
		
		try
		{
			$this->_mongodb->$table->update($query, array('$set'=>$data), array('safe'=>TRUE,'multiple'=>TRUE));
			return TRUE;
		}
		catch (MongoCursorException $e)
		{
			$error = $e->getMessage();
			return FALSE;
		}
	}
	
	/*
	 * 删除数据
	 * table	表名
	 * condition	条件，数组
	 */
	public function delete($table, $condition = array())
	{
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		
		$query = $this->parse_query($condition);

		try
		{
			$this->_mongodb->$table->remove($query, array('safe'=>TRUE));
			return TRUE;
		}
		catch (MongoCursorException $e)
		{
			$error = $e->getMessage();
			return FALSE;
		}
	}
	
	/*
	 * 统计总数
	 * 成功返回统计数量，失败返回false
	 */
	public function count($table, $condition = array())
	{
		if ( ! $this->table_exist($table))
		{
			$this->error('数据表'.$table.'不存在');
			return FALSE;
		}
		$query = $this->parse_query($condition);
		return $this->_mongodb->selectCollection($table)->find($query)->count();
	}
	
	/*
	 * 分析查询条件
	 * 返回查询条件数组
	 * 注意：条件类型很重要，整形不能写成字符型
	 */
	protected function parse_query($condition)
	{
		$query = array();
		if ( ! $condition)
		{
			return $query;
		}
		
		if ( ! is_array($condition))
		{
			$condition = array(array('id', $condition));
		}

		foreach ($condition as $row)
		{
			$name = $row[0];
			$value = $row[1];
			$exp = isset($row[2]) ? $row[2] : '';
			
			switch ($name)
			{
				case $this->_pk :
					$name = '_id';
					$value = new MongoId($value);
					break;
				default :
					break;
			}

			switch ($exp)
			{
				case '' :
				case '=' :
					$query[$name] = $value;
					break;
				case 'gt' :
				case '>' :
					$query[$name] = array('$gt'=>$value);
					break;
				case 'lt' :
				case '<' :
					$query[$name] = array('$lt'=>$value);
					break;
				case 'gte' :
				case '>=' :
					$query[$name] = array('$gte'=>$value);
					break;
				case 'lte' :
				case '<=' :
					$query[$name] = array('$lte'=>$value);
					break;
				case 'ne' :
				case '!=' :
					$query[$name] = array('$ne'=>$value);
					break;
				case 'in' :
					//$value是个数组
					$query[$name] = array('$in'=>(array)$value);
					break;
				case 'nin' :
					//$value是个数组
					$query[$name] = array('$nin'=>(array)$value);
					break;
				case 'mod' :
					//$value是个一维数组，如：（10,2），即字段$name的值除以10余数等于2，则条件成立
					$query[$name] = array('$mod'=>(array)$value);
					break;
				case 'all' :
					//字段$name必须是个数组，类似于字符串相等，这里指的是数组
					$query[$name] = array('$all'=>(array)$value);
					break;
				case 'size' :
					//字段$name必须是个数组，且数组长度等于$value
					$query[$name] = array('$size'=>intval($value));
					break;
				case 'exists' :
					//字段值是否存在
					$query[$name] = array('$exists'=>($value ? TRUE : FALSE));
					break;
				case 'type' :
					//是否字段类型。1double，2string，3object，4array，5binary data，7object id，8boolean，9date，10null等等（其他几乎不用的见手册）
					$query[$name] = array('$type'=>intval($value));
					break;
				case 'reg' :
					//正则表达式，强大的家伙
					$query[$name] = new MongoRegex($value);
					break;
				default :
					break;
			}
		}
		return $query;
	}
	
	/*
	 * 分析排序
	 * 返回排序条件数组
	 */
	protected function parse_sort($condition)
	{
		$sort = array();
		if ($condition)
		{
			foreach ($condition as $row)
			{
				$name = $row[0];
				$value = $row[1];
				$exp = isset($row[2]) ? $row[2] : '';
				if ($exp=='sort')
				{
					$sort[$name] = $value ? 1 : -1;
				}
			}
		}
		if ( ! $sort)
		{
			$sort['timestamp'] = -1;
		}
		return $sort;
	}
	
	/*
	 * 解析limit
	 * 返回skip,fetch组成的数组
	 */
	protected function parse_limit($limit)
	{
		$skip = 0;
		$fetch = 0;

		if (is_numeric($limit))
		{
			$fetch = intval($limit);
		}
		else
		{
			if ( ! is_array($limit))
			{
				$limit = explode(',', $limit);
			}

			if (isset($limit[1]))
			{
				$skip = intval($limit[0]);
				$fetch = intval($limit[1]);
			}
			else
			{
				$fetch = intval($limit[0]);
			}
		}
		
		return array($skip, $fetch);
	}
	
	/*
	 * 解析数据
	 */
	protected function parse_data($data)
	{
		$_data = array();
		if ($data)
		{
			foreach ($data as $k => $v)
			{
				$_data[$k] = $v;
			}
		}
		return $_data;
	}
	
	/*
	 * 解析字段
	 */
	protected function parse_fields($fields)
	{
		$_fields = array();
		if ($fields)
		{
			if ( ! is_array($fields))
			{
				$_fields = explode(',', $fields);
			}
		}
		
		if ($_fields)
		{
			foreach ($_fields as &$field)
			{
				$field = trim($field);
			}

			$this->_return_pk = in_array($this->_pk, $_fields) ? TRUE : FALSE;
		}

		return $_fields;
	}
	
	/*
	 * 解析游标，返回结果数组
	 */
	protected function parse_cursor()
	{
		$results = iterator_to_array($this->_cursor);
		$_results = array();
		if ($results)
		{
			foreach ($results as &$result)
			{
				if ($this->_return_pk)
				{
					$result[$this->_pk] = strval($result['_id']);
				}
				unset($result['_id']);
				$_results[] = $result;
			}
		}
		return $_results;
	}
	
}